package com.example.redis.config;

import com.example.redis.common.constant.CacheConstants;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.SimpleCacheErrorHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.CacheKeyPrefix;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;


@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {


    /**
     * 前缀
     *
     * @return
     */
    @Bean
    public CacheKeyPrefix cacheKeyPrefix() {
        return new CacheKeyPrefix() {
            @Override
            public String compute(String cacheName) {
                StringBuilder sBuilder = new StringBuilder(100);
                sBuilder.append("cache").append(":");
                sBuilder.append(cacheName).append(":");
                return sBuilder.toString();
            }
        };
    }

    @Bean("redisTemplate")
    public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) {

        //使用GenericJackson2JsonRedisSerializer序列化时，会保存序列化的对象的包名和类名，反序列化时以这个作为标示就可以反序列化成指定的对象。

        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);

        // key的序列化采用StringRedisSerializer
        redisTemplate.setKeySerializer(new StringRedisSerializer());

        redisTemplate.setHashKeySerializer(new StringRedisSerializer());

        // 使用Jackson2JsonRedisSerialize 替换默认序列化
        // value值的序列化采用 Jackson2
        redisTemplate.setDefaultSerializer(jackson2JsonRedisSerializer());
        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }


    @Bean
    public Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
        final Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(
                Object.class);
        final ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build();
        objectMapper.disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES);
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //属性值为null的不缓存
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);


        //序列化方式，
        // {
        //     "@class": "com.example.redis.domain.UserDTO",
        //         "username": "name1",
        //         "age": 29
        // }
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);


        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        return jackson2JsonRedisSerializer;
    }

    // @Bean
    // public CacheManager cacheManager(){
    //     CaffeineCacheManager cacheManager=new CaffeineCacheManager();
    //     cacheManager.setCaffeine(Caffeine.newBuilder()
    //             .initialCapacity(128)
    //             .maximumSize(1024)
    //             .expireAfterWrite(60, TimeUnit.SECONDS));
    //     return cacheManager;
    // }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();

        //设置序列化方式
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(30))// 设置缓存的默认过期时间，也是使用Duration设置
                .computePrefixWith(cacheKeyPrefix())//使用统一前缀
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer()))
                .disableCachingNullValues(); // 不缓存空值


        // 对每个缓存空间应用不同的配置
        Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
        //失效时间:单位:秒
        configMap.put(CacheConstants.USER_DETAILS, config.entryTtl(Duration.ofMinutes(30)));

        // 使用自定义的缓存配置初始化一个cacheManager
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)//必须配置默认的策略
                .withInitialCacheConfigurations(configMap)  //失效时间的设置
                .build();

        return cacheManager;
    }


    // @Bean
    // public RedisSerializer<Object> redisSerializer() {
    //     //创建JSON序列化器
    //     Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
    //     ObjectMapper objectMapper = new ObjectMapper();
    //     objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    //     //必须设置，否则无法将JSON转化为对象，会转化成Map类型
    //     objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL);
    //     serializer.setObjectMapper(objectMapper);
    //     return serializer;
    // }
    //


    /**
     * 自定义redis序列化的机制,重新定义一个ObjectMapper.防止和MVC的冲突
     * https://juejin.im/post/5e869d426fb9a03c6148c97e
     */
    // @Bean
    // public RedisSerializer<Object> redisSerializer() {
    //     ObjectMapper objectMapper = JsonMapper.builder().disable(MapperFeature.USE_ANNOTATIONS).build();
    //     // 反序列化时候遇到不匹配的属性并不抛出异常
    //     objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    //     // 序列化时候遇到空对象不抛出异常
    //     objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
    //     // 反序列化的时候如果是无效子类型,不抛出异常
    //     objectMapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
    //     // 不使用默认的dateTime进行序列化,
    //     objectMapper.configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false);
    //     // 使用JSR310提供的序列化类,里面包含了大量的JDK8时间序列化类
    //     objectMapper.registerModule(new JavaTimeModule());
    //     // 启用反序列化所需的类型信息,在属性中添加@class
    //     objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL,
    //             JsonTypeInfo.As.PROPERTY);
    //     // 配置null值的序列化器
    //     GenericJackson2JsonRedisSerializer.registerNullValueSerializer(objectMapper, null);
    //     return new GenericJackson2JsonRedisSerializer(objectMapper);
    }


    /**
     * 如果cache出错， 我们会记录在日志里，方便排查，比如反序列化异常
     */
    @Override
    public CacheErrorHandler errorHandler() {
        return new LoggingCacheErrorHandler();
    }

    static class LoggingCacheErrorHandler extends SimpleCacheErrorHandler {

        private final Logger logger = LoggerFactory.getLogger(this.getClass());

        @Override
        public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
            logger.error(String.format("cacheName:%s,cacheKey:%s", cache == null ? "unknown" : cache.getName(), key), exception);
            //            super.handleCacheGetError(exception, cache, key);  不抛出错误，记录日志
        }

        @Override
        public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
            logger.error(String.format("cacheName:%s,cacheKey:%s", cache == null ? "unknown" : cache.getName(), key), exception);
            //            super.handleCachePutError(exception, cache, key, value);不抛出错误，记录日志
        }

        @Override
        public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
            logger.error(String.format("cacheName:%s,cacheKey:%s", cache == null ? "unknown" : cache.getName(), key), exception);
            //            super.handleCacheEvictError(exception, cache, key);不抛出错误，记录日志
        }

        @Override
        public void handleCacheClearError(RuntimeException exception, Cache cache) {
            logger.error(String.format("cacheName:%s", cache == null ? "unknown" : cache.getName()), exception);
            //            super.handleCacheClearError(exception, cache);不抛出错误，记录日志
        }
    }

}